#!/usr/bin/env ruby

# Copyright (c) 2021-2024 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

include_relative 'common.irt'
include_relative 'string_helpers.irt'

GenerateStringEquals(lang='', dynamic=false, compressed=true)
GenerateStringEquals(lang='', dynamic=false, compressed=false)
GenerateCreateStringFromStringTlab(string_compression_enabled=true)
GenerateCreateStringFromStringTlab(string_compression_enabled=false)
GenerateCreateStringFromCharArrayTlab(string_compression_enabled=true)
GenerateCreateStringFromCharArrayTlab(string_compression_enabled=false)
GenerateCreateStringFromZeroBasedCharArrayTlab(string_compression_enabled=true)
GenerateCreateStringFromZeroBasedCharArrayTlab(string_compression_enabled=false)
GenerateSubstringFromStringTlab(string_compression_enabled=true)
GenerateSubstringFromStringTlab(string_compression_enabled=false)
GenerateStringGetCharsTlab(string_compression_enabled=true)
GenerateStringGetCharsTlab(string_compression_enabled=false)
GenerateStringHashCode(string_compression_enabled=true)
GenerateStringHashCode(string_compression_enabled=false)

available_regs = $panda_mask
function(:StringConcat2Tlab,
          params: {str1: 'ref', str2: 'ref'},
          regmap: $full_regmap,
          regalloc_set: available_regs,
          mode: [:FastPath]) {

    if Options.arch == :arm32
      Intrinsic(:UNREACHABLE).void.Terminator
      next
    end

    klass := load_class(str1)
    length1 := LoadI(str1).Imm(Constants::STRING_LENGTH_OFFSET).u32
    length2 := LoadI(str2).Imm(Constants::STRING_LENGTH_OFFSET).u32

    # any of the strings is uncompressed (resulted string is uncompressed)
    has_uncompressed := AndI(Or(length1, length2).u32).Imm(1).u32

    count1 := ShrI(length1).Imm(1).u32
    count2 := ShrI(length2).Imm(1).u32

    size := Add(count1, count2).u32
    data_size := Shl(size, has_uncompressed).u32
    length := Or(ShlI(size).Imm(1).u32, has_uncompressed).u32
    new_str := allocate_string_tlab(klass, data_size)

    StoreI(new_str, length).Imm(Constants::STRING_LENGTH_OFFSET).u32
    StoreI(new_str, Cast(0).u32).Imm(Constants::STRING_HASHCODE_OFFSET).u32

    src_str_data1 := AddI(str1).Imm(Constants::STRING_DATA_OFFSET).ptr
    dst_str_data1 := AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr

    # has no uncompressed then everyting is compressed, data is stored as bytes
    If(has_uncompressed, 0).EQ.b {
      copy_u8_chars(src_str_data1, dst_str_data1, count1)

      offset := count1

      src_str_data2 := AddI(str2).Imm(Constants::STRING_DATA_OFFSET).ptr
      dst_str_data2 := Add(AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr, offset).ptr
      copy_u8_chars(src_str_data2, dst_str_data2, count2)
      Goto(:EndCopy)
    }

    If(AndI(length1).Imm(1).u32, 0).EQ.b {
      expand_u8_to_u16_chars(src_str_data1, dst_str_data1, count1)
    } Else {
      copy_u16_chars(src_str_data1, dst_str_data1, count1)
    }

    offset := ShlI(count1).Imm(1).u32
    src_str_data2 := AddI(str2).Imm(Constants::STRING_DATA_OFFSET).ptr
    dst_str_data2 := Add(AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr, offset).ptr

    If(AndI(length2).Imm(1).u32, 0).EQ.b {
      expand_u8_to_u16_chars(src_str_data2, dst_str_data2, count2)
    } Else {
      copy_u16_chars(src_str_data2, dst_str_data2, count2)
    }

    Label(:EndCopy)

    # String is supposed to be a constant object, so all its data should be visible by all threads
    Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
    Return(new_str).ptr

Label(:SlowPathEntrypoint)
    ep_offset = get_entrypoint_offset("STRING_CONCAT2_SLOW_PATH")
    Intrinsic(:SLOW_PATH_ENTRY, str1, str2).AddImm(ep_offset).MethodAsImm("StringConcat2UsualBridge").Terminator.ptr
    Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
}

available_regs = $panda_mask
function(:StringConcat3Tlab,
         params: {str1: 'ref', str2: 'ref', str3: 'ref'},
         regmap: $full_regmap,
         regalloc_set: available_regs,
         mode: [:FastPath]) {

    if Options.arch == :arm32
      Intrinsic(:UNREACHABLE).void.Terminator
      next
    end

    klass := load_class(str1)
    length1 := LoadI(str1).Imm(Constants::STRING_LENGTH_OFFSET).u32
    length2 := LoadI(str2).Imm(Constants::STRING_LENGTH_OFFSET).u32
    length3 := LoadI(str3).Imm(Constants::STRING_LENGTH_OFFSET).u32

    has_uncompressed := AndI(Or(length3, Or(length1, length2).u32).u32).Imm(1).u32

    count1 := ShrI(length1).Imm(1).u32
    count2 := ShrI(length2).Imm(1).u32
    count3 := ShrI(length3).Imm(1).u32

    size := Add(count1, Add(count2, count3).u32).u32
    data_size := Shl(size, has_uncompressed).u32
    length := Or(ShlI(size).Imm(1).u32, has_uncompressed).u32
    new_str := allocate_string_tlab(klass, data_size)

    StoreI(new_str, length).Imm(Constants::STRING_LENGTH_OFFSET).u32
    StoreI(new_str, Cast(0).u32).Imm(Constants::STRING_HASHCODE_OFFSET).u32

    src_str_data1 := AddI(str1).Imm(Constants::STRING_DATA_OFFSET).ptr
    dst_str_data1 := AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr

    offset1 := ShlI(count1).Imm(1).u32
    offset2 := Add(offset1, ShlI(count2).Imm(1).u32).u32

    # everything is compressed
    If(has_uncompressed, 0).EQ.b {
      copy_u8_chars(src_str_data1, dst_str_data1, count1)

      offset1_ := ShrI(offset1).Imm(1).u32

      src_str_data2 := AddI(str2).Imm(Constants::STRING_DATA_OFFSET).ptr
      dst_str_data2 := Add(AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr, offset1_).ptr
      copy_u8_chars(src_str_data2, dst_str_data2, count2)

      offset2_ := ShrI(offset2).Imm(1).u32

      src_str_data3 := AddI(str3).Imm(Constants::STRING_DATA_OFFSET).ptr
      dst_str_data3 := Add(AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr, offset2_).ptr
      copy_u8_chars(src_str_data3, dst_str_data3, count3)
      Goto(:EndCopy)
    }

    If(AndI(length1).Imm(1).u32, 0).EQ.b {
      expand_u8_to_u16_chars(src_str_data1, dst_str_data1, count1)
    } Else {
      copy_u16_chars(src_str_data1, dst_str_data1, count1)
    }

    src_str_data2 := AddI(str2).Imm(Constants::STRING_DATA_OFFSET).ptr
    dst_str_data2 := Add(AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr, offset1).ptr

    If(AndI(length2).Imm(1).u32, 0).EQ.b {
      expand_u8_to_u16_chars(src_str_data2, dst_str_data2, count2)
    } Else {
      copy_u16_chars(src_str_data2, dst_str_data2, count2)
    }

    src_str_data3 := AddI(str3).Imm(Constants::STRING_DATA_OFFSET).ptr
    dst_str_data3 := Add(AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr, offset2).ptr

    If(AndI(length3).Imm(1).u32, 0).EQ.b {
      expand_u8_to_u16_chars(src_str_data3, dst_str_data3, count3)
    } Else {
      copy_u16_chars(src_str_data3, dst_str_data3, count3)
    }

Label(:EndCopy)
    # String is supposed to be a constant object, so all its data should be visible by all threads
    Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
    Return(new_str).ptr

Label(:SlowPathEntrypoint)
    ep_offset = get_entrypoint_offset("STRING_CONCAT3_SLOW_PATH")
    Intrinsic(:SLOW_PATH_ENTRY, str1, str2, str3).AddImm(ep_offset).MethodAsImm("StringConcat3OddSavedBridge").Terminator.ptr
    Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
}

available_regs = $panda_mask
function(:StringConcat4Tlab,
         params: {str1: 'ref', str2: 'ref', str3: 'ref', str4: 'ref'},
         regmap: $full_regmap,
         regalloc_set: available_regs,
         mode: [:FastPath]) {
    if Options.arch == :arm32
      Intrinsic(:UNREACHABLE).void.Terminator
      next
    end

    klass := load_class(str1)
    length1 := LoadI(str1).Imm(Constants::STRING_LENGTH_OFFSET).u32
    length2 := LoadI(str2).Imm(Constants::STRING_LENGTH_OFFSET).u32
    length3 := LoadI(str3).Imm(Constants::STRING_LENGTH_OFFSET).u32
    length4 := LoadI(str4).Imm(Constants::STRING_LENGTH_OFFSET).u32

    # any of the strings is uncompressed (resulted string is uncompressed)
    has_uncompressed := Or(length3, Or(length1, length2).u32).u32
    has_uncompressed := AndI(Or(length4, has_uncompressed).u32).Imm(1).u32

    count1 := ShrI(length1).Imm(1).u32
    count2 := ShrI(length2).Imm(1).u32
    count3 := ShrI(length3).Imm(1).u32
    count4 := ShrI(length4).Imm(1).u32

    size := Add(count1, Add(count2, Add(count3, count4).u32).u32).u32
    data_size := Shl(size, has_uncompressed).u32
    length := Or(ShlI(size).Imm(1).u32, has_uncompressed).u32
    new_str := allocate_string_tlab(klass, data_size)

    StoreI(new_str, length).Imm(Constants::STRING_LENGTH_OFFSET).u32
    StoreI(new_str, Cast(0).u32).Imm(Constants::STRING_HASHCODE_OFFSET).u32

    src_str_data1 := AddI(str1).Imm(Constants::STRING_DATA_OFFSET).ptr
    dst_str_data1 := AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr

    offset1 := ShlI(count1).Imm(1).u32
    offset2 := Add(offset1, ShlI(count2).Imm(1).u32).u32
    offset3 := Add(offset2, ShlI(count3).Imm(1).u32).u32

    # has no uncompressed then everything is compressed, data is stored as bytes
    If(has_uncompressed, 0).EQ.b {
      copy_u8_chars(src_str_data1, dst_str_data1, count1)

      offset1_ := ShrI(offset1).Imm(1).u32

      src_str_data2 := AddI(str2).Imm(Constants::STRING_DATA_OFFSET).ptr
      dst_str_data2 := Add(AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr, offset1_).ptr
      copy_u8_chars(src_str_data2, dst_str_data2, count2)

      offset2_ := ShrI(offset2).Imm(1).u32

      src_str_data3 := AddI(str3).Imm(Constants::STRING_DATA_OFFSET).ptr
      dst_str_data3 := Add(AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr, offset2_).ptr
      copy_u8_chars(src_str_data3, dst_str_data3, count3)

      offset3_ := ShrI(offset3).Imm(1).u32

      src_str_data4 := AddI(str4).Imm(Constants::STRING_DATA_OFFSET).ptr
      dst_str_data4 := Add(AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr, offset3_).ptr
      copy_u8_chars(src_str_data4, dst_str_data4, count4)
      Goto(:EndCopy)
    }

    If(AndI(length1).Imm(1).u32, 0).EQ.b {
      expand_u8_to_u16_chars(src_str_data1, dst_str_data1, count1)
    } Else {
      copy_u16_chars(src_str_data1, dst_str_data1, count1)
    }

    src_str_data2 := AddI(str2).Imm(Constants::STRING_DATA_OFFSET).ptr
    dst_str_data2 := Add(AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr, offset1).ptr

    If(AndI(length2).Imm(1).u32, 0).EQ.b {
      expand_u8_to_u16_chars(src_str_data2, dst_str_data2, count2)
    } Else {
      copy_u16_chars(src_str_data2, dst_str_data2, count2)
    }

    src_str_data3 := AddI(str3).Imm(Constants::STRING_DATA_OFFSET).ptr
    dst_str_data3 := Add(AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr, offset2).ptr

    If(AndI(length3).Imm(1).u32, 0).EQ.b {
      expand_u8_to_u16_chars(src_str_data3, dst_str_data3, count3)
    } Else {
      copy_u16_chars(src_str_data3, dst_str_data3, count3)
    }

    src_str_data4 := AddI(str4).Imm(Constants::STRING_DATA_OFFSET).ptr
    dst_str_data4 := Add(AddI(new_str).Imm(Constants::STRING_DATA_OFFSET).ptr, offset3).ptr

    If(AndI(length4).Imm(1).u32, 0).EQ.b {
      expand_u8_to_u16_chars(src_str_data4, dst_str_data4, count4)
    } Else {
      copy_u16_chars(src_str_data4, dst_str_data4, count4)
    }
Label(:EndCopy)
    # String is supposed to be a constant object, so all its data should be visible by all threads
    Intrinsic(:DATA_MEMORY_BARRIER_FULL).void
    Return(new_str).ptr

Label(:SlowPathEntrypoint)
    ep_offset = get_entrypoint_offset("STRING_CONCAT4_SLOW_PATH")
    Intrinsic(:SLOW_PATH_ENTRY, str1, str2, str3, str4).AddImm(ep_offset).MethodAsImm("StringConcat4UsualBridge").Terminator.ptr
    Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
}

function(:StringCompareTo,
         params: {str1: 'ref', str2: 'ref'},
         regmap: $full_regmap,
         regalloc_set: $panda_mask,
         mode: [:FastPath]) {
  # Arm32 is not supported
  if Options.arch == :arm32
    Intrinsic(:UNREACHABLE).void.Terminator
    next
  end

  If(str1, str2).EQ.Unlikely.b {
    Return(0).i32
  }

  length1 := LoadI(str1).Imm(Constants::STRING_LENGTH_OFFSET).u32
  length2 := LoadI(str2).Imm(Constants::STRING_LENGTH_OFFSET).u32

  # cover the cases of length2 being zero and both lengths being zero
  If(length2, 0).EQ.Unlikely.b {
    Return(length1).i32
  }

  If(length1, 0).EQ.Unlikely.b {
    Return(-1).i32
  }

  buf1 := AddI(str1).Imm(Constants::STRING_DATA_OFFSET).ptr
  buf2 := AddI(str2).Imm(Constants::STRING_DATA_OFFSET).ptr

  # get the least length in chars
  length := ShrI(Cast(Min(length1, length2).i32).u64).Imm(1).u64

  utf_1 := AndI(length1).Imm(1).u32
  utf_2 := AndI(length2).Imm(1).u32

  # in the unlikely case the strings are in different 
  # encodings the comparison gets more comlicated as
  # we have to expand the latin string to u16 first
  If(utf_1, utf_2).NE.Unlikely.b {
    Return(compare_mixed_strings(buf1, buf2, length, utf_1).i32).i32
  }
  utf := Cast(utf_1).u64

  # make length be in actual bytes now
  length := Shl(length, utf).u64

  # considering the data buffer to be allocated to
  # 8-bytes alignment, we can safely read 8-bytes chunks
  last_idx := SubI(length).Imm(8).i64
  i1 := 0
Label(:Loop)
  i := Phi(i1, i2).i64
  If(i, last_idx).GE.Unlikely.b {
    # can safely read 8 bytes behind - length and hashcode
    junk_bits := ShlI(Sub(i, last_idx).u64).Imm(3).u64
    last_data1 := Shr(Load(buf1, last_idx).u64, junk_bits).u64
    last_data2 := Shr(Load(buf2, last_idx).u64, junk_bits).u64
    If(last_data1, last_data2).NE.b {
      Goto(:NotEqual)
    }
    Return(Sub(length1, length2).i32).i32
  }

  data1 := Load(buf1, i).u64
  data2 := Load(buf2, i).u64
  If(data1, data2).NE.b {
    Goto(:NotEqual)
  }
  i2 := AddI(i).Imm(8).i64
  Goto(:Loop)

Label(:NotEqual)
  d1 := Phi(last_data1, data1).u64
  d2 := Phi(last_data2, data2).u64
  Return(calculate_chars_difference(d1, d2, utf).i32).i32
}

include_plugin 'ets_string'
